/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.googlecode.mindlistening.freemind;

import com.googlecode.mindlistening.audio.AudioPlayer;
import com.googlecode.mindlistening.AudioPlayerListener;
import freemind.controller.Controller;
import freemind.modes.ModeController.NodeSelectionListener;
import freemind.modes.mindmapmode.MindMapController;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javazoom.jlgui.basicplayer.BasicPlayer;
import javazoom.jlgui.basicplayer.BasicPlayerException;
import javazoom.jlgui.basicplayer.BasicPlayerListener;

/**
 *
 * @author ryan
 */
public class Player implements BasicPlayerListener, AudioPlayer, NodeSelectionListener {

    SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy");
    List<AudioPlayerListener> listeners = new ArrayList<AudioPlayerListener>();
    javazoom.jlgui.basicplayer.BasicController control;
    long secondsAmount = 0;
    Map audioInfo;
    Controller controller;
    boolean stopRequested = false;
    LastSelectedNodeInfo lastSelectedNodeInfo;

    public Player(MindMapController mm_controller) {
        this.controller = mm_controller.getController();
    }

    // AudioPlayer ***********************
    @Override
    public long play(File audioFile) {
        stopRequested = false;
        controller.getModeController().registerNodeSelectionListener(this);

        // Instantiate javazoom.jlgui.basicplayer.BasicPlayer.
        BasicPlayer player = new javazoom.jlgui.basicplayer.BasicPlayer();
        control = (javazoom.jlgui.basicplayer.BasicController) player;
        player.addBasicPlayerListener(this);
        try {
            // Open file, or URL or Stream (shoutcast, icecast) to play.
            control.open(audioFile);
            control.play();
        } catch (javazoom.jlgui.basicplayer.BasicPlayerException e) {
            e.printStackTrace();
        }
        return 1;
    }

    public void pause() {
        try {
            control.pause();
        } catch (BasicPlayerException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void resume() {
        try {
            control.resume();
        } catch (BasicPlayerException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void stop() {
        try {
            stopRequested = true;
            control.stop();
            controller.getModeController().deregisterNodeSelectionListener(this);
        } catch (BasicPlayerException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Seek to to seconds into track
     * @param percent
     */
    public void seek(long seconds) {
        if (stopRequested) {
            return;
        }
        long totalTime = (long) Math.round(getTimeLengthEstimation(audioInfo) / 1000);
        long byteslength = -1;
        if (audioInfo.containsKey("audio.length.bytes")) {
            byteslength = ((Integer) audioInfo.get("audio.length.bytes")).intValue();
        }
        System.out.println("Seconds: " + seconds);
        System.out.println("totalTime: " + totalTime);
        System.out.println("byteslength: " + byteslength);


        long bytesToSkip = (long) (((double) seconds / (double) totalTime) * byteslength);
        bytesToSkip = bytesToSkip - (bytesToSkip % 4);
        System.out.println("bytesToSkip: " + bytesToSkip);
        // the ogg file seems to be in frame sizes of 4 bytes. so round to a divisable by 4



        try {
            control.seek(bytesToSkip);
            System.out.println("Seek done");
        } catch (BasicPlayerException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void addListener(com.googlecode.mindlistening.AudioPlayerListener listener) {
        listeners.add(listener);
    }

    public void removerListener(com.googlecode.mindlistening.AudioPlayerListener listener) {
        listeners.remove(listener);
    }

    //Basic Player ***********************
    public void opened(Object stream, Map properties) {
        audioInfo = properties;
        for (AudioPlayerListener apl : listeners) {
            apl.started();
        }
    }

    public void progress(int bytesread, long microseconds, byte[] pcmdata, Map properties) {
        int byteslength = -1;
        long total = -1;
        // try with JavaSound SPI.
        if (total <= 0) {
            total = (long) Math.round(getTimeLengthEstimation(audioInfo) / 1000);
        }
        if (audioInfo.containsKey("audio.length.bytes")) {
            byteslength = ((Integer) audioInfo.get("audio.length.bytes")).intValue();
        }
        float progress = -1.0f;
        if ((bytesread > 0) && ((byteslength > 0))) {
            progress = bytesread * 1.0f / byteslength * 1.0f;
        }
        if (audioInfo.containsKey("audio.type")) {
            String audioformat = (String) audioInfo.get("audio.type");
            if (audioformat.equalsIgnoreCase("mp3")) {
                if (total > 0) {
                    secondsAmount = (long) (total * progress);
                } else {
                    secondsAmount = -1;
                }
            } else if (audioformat.equalsIgnoreCase("wave")) {
                secondsAmount = (long) (total * progress);
            } else {
                secondsAmount = (long) Math.round(microseconds / 1000000);
            }
        } else {
            secondsAmount = (long) Math.round(microseconds / 1000000);
        }
        if (secondsAmount < 0) {
            secondsAmount = (long) Math.round(microseconds / 1000000);
        }
        for (AudioPlayerListener apl : listeners) {
            apl.playing(secondsAmount, total);
        }
    }

    public void stateUpdated(javazoom.jlgui.basicplayer.BasicPlayerEvent event) {
        //System.out.println("Event: " + event.getCode() + " " + event.getDescription());
        if (event.getCode() == javazoom.jlgui.basicplayer.BasicPlayerEvent.EOM) {
            for (AudioPlayerListener apl : listeners) {
                apl.stopped();
            }
        }
        if (event.getCode() == javazoom.jlgui.basicplayer.BasicPlayerEvent.STOPPED && stopRequested) {

            for (AudioPlayerListener apl : listeners) {
                //System.out.println("Forced stop");
                apl.stopped();
            }
        }
    }

    public void setController(javazoom.jlgui.basicplayer.BasicController controller) {
        this.control = controller;
    }

    public long getTimeLengthEstimation(Map properties) {
        long milliseconds = -1;
        int byteslength = -1;
        if (properties != null) {
            if (properties.containsKey("audio.length.bytes")) {
                byteslength = ((Integer) properties.get("audio.length.bytes")).intValue();
            }
            if (properties.containsKey("duration")) {
                milliseconds = (((Long) properties.get("duration")).longValue()) / 1000;
            } else {
                // Try to compute duration
                int bitspersample = -1;
                int channels = -1;
                float samplerate = -1.0f;
                int framesize = -1;
                if (properties.containsKey("audio.samplesize.bits")) {
                    bitspersample = ((Integer) properties.get("audio.samplesize.bits")).intValue();
                }
                if (properties.containsKey("audio.channels")) {
                    channels = ((Integer) properties.get("audio.channels")).intValue();
                }
                if (properties.containsKey("audio.samplerate.hz")) {
                    samplerate = ((Float) properties.get("audio.samplerate.hz")).floatValue();
                }
                if (properties.containsKey("audio.framesize.bytes")) {
                    framesize = ((Integer) properties.get("audio.framesize.bytes")).intValue();
                }
                if (bitspersample > 0) {
                    milliseconds = (int) (1000.0f * byteslength / (samplerate * channels * (bitspersample / 8)));
                } else {
                    milliseconds = (int) (1000.0f * byteslength / (samplerate * framesize));
                }
            }
        }
        return milliseconds;
    }

    // NodeSelectionListener
    public void onSelectHook(freemind.view.mindmapview.NodeView node) {
        try {

            lastSelectedNodeInfo = new LastSelectedNodeInfo();

            Date date = node.getModel().getHistoryInformation().getCreatedAt();
            String start = controller.getView().getRoot().getModel().getAttribute("startDate");
            //def start = root.getAttribute("startDate");
            Date startDate = formatter.parse(start);
            String end = controller.getView().getRoot().getModel().getAttribute("endDate");
            Date endDate = formatter.parse(end);
            // make sure between
            if (!date.after(startDate) || !date.before(endDate)) {
                return;
            }
            long seconds = (date.getTime() - startDate.getTime()) / 1000;
            lastSelectedNodeInfo.originalStart = seconds;
            //System.out.println("Node timestamp: " + seconds);
            int tagStartOffset = 15;
            try {
                String tagStartOffset_str = controller.getProperty("tagStartOffset");
                tagStartOffset = Integer.parseInt(tagStartOffset_str);
            } catch (Exception e) {
            }
            if (seconds > tagStartOffset) {
                seconds = seconds - tagStartOffset;
            }
            lastSelectedNodeInfo.offsetStart = seconds;
            int tagDuration = 15;
            try {
                String tagDuration_str = controller.getProperty("tagDuration");
                tagDuration = Integer.parseInt(tagDuration_str);
            } catch (Exception e) {
            }
            lastSelectedNodeInfo.end = seconds + tagDuration;

            //System.out.println("Seconds to seek to (with offset): " + seconds);
            seek(seconds);
            for (AudioPlayerListener apl : listeners) {
                apl.nodeSelected(lastSelectedNodeInfo.offsetStart, lastSelectedNodeInfo.originalStart, lastSelectedNodeInfo.end);
            }
        } catch (ParseException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }


    }

    public void onUpdateNodeHook(freemind.modes.MindMapNode node) {
    }

    public void onDeselectHook(freemind.view.mindmapview.NodeView node) {
    }

    ;

    public void onSaveNode(freemind.modes.MindMapNode node) {
    }

    private class LastSelectedNodeInfo {

        public long offsetStart;
        public long originalStart;
        public long end;
    }
}
